﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Globalization;
using System.Security.Cryptography;
using System.Web;
//using System.Runtime.Serialization.Json;
using System.Threading;

namespace WebQQ
{
    
    static class HttpHelper 
    {
        private static CookieContainer cookieContainer = new CookieContainer();
        private static String refer = "http://d.web2.qq.com/proxy.html";
        private static String userAgent = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 1.1.4322; .NET4.0C; .NET4.0E)";
        private static String accept = "*/*";
        private static String contentType = "application/x-www-form-urlencoded; charset=UTF-8";
        static Dictionary<string, string> m_cookies = new Dictionary<string, string>();

        public static HttpWebResponse Request(string url, byte[] data = null)
        {
            var request = (HttpWebRequest)WebRequest.Create(url);
            request.UserAgent = userAgent;
            request.Accept = accept;
            request.ContentType = contentType;
            request.Referer = refer;
            request.CookieContainer = cookieContainer;

            if (data != null)
            {
                request.Method = "POST";
                request.ContentLength = data.Length;
                using (var stream = request.GetRequestStream())
                {
                    stream.Write(data, 0, data.Length);
                }
            }
            else
            {
                request.Method = "GET";
            }

            var response = (HttpWebResponse)request.GetResponse();
            foreach (Cookie cookie in response.Cookies)
            {
                m_cookies[cookie.Name] = cookie.Value;
                cookieContainer.Add(cookie);
            }
            return response;
        }

        public static string RequestString(string url, byte[] data = null)
        {
            using (var response = Request(url, data))
            {
                using (var stream = response.GetResponseStream())
                {
                    using (var sr = new StreamReader(stream, Encoding.UTF8))
                    {
                        return sr.ReadToEnd();
                    }
                }
            }
        }

        public static byte[] RequestData(string url)
        {
            using (var response = Request(url))
            {
                using (var stream = response.GetResponseStream())
                {
                    List<byte> data = new List<byte>();
                    while (true) 
                    {
                        var n = stream.ReadByte();
                        if (n == -1) break;
                        data.Add((byte)n);
                    }

                    return data.ToArray();
                }
            }
        }

        public static string GetCookie(string key)
        {
            if (!m_cookies.ContainsKey(key))
            {
                return string.Empty;
            }
            return m_cookies[key];
        }
    }

    public delegate string VerifyCallback(byte[] data);

    public delegate void MessageEventHandler(object sender, MessageEventArgs e);

    public class MessageEventArgs : EventArgs 
    {
        static readonly DateTime Utc = DateTime.Parse("1970/1/1 8:00:00");

        public MessageEventArgs(QQClient client, dynamic json)
        {
            var uin = json.from_uin;
            QQ = client.Friends.FirstOrDefault(f => f.UIN == uin);
            Time = Utc + TimeSpan.FromSeconds(long.Parse(json.time));
            IP = new IPAddress(long.Parse(json.reply_ip));
            Message = json.content[1];
        }

        public QQFriend QQ { get; private set; }

        public DateTime Time { get; private set; }

        public IPAddress IP { get; private set; }

        public string Message { get; private set; }
    }

    public class QQClient
    {
        const string QQString1 = "http://check.ptlogin2.qq.com/check?appid=1003903&uin={0}&r={1}";
        const string QQString2 = "http://captcha.qq.com/getimage?aid=1003903&uin={0}&r={1}";
        const string QQString3 = "http://ptlogin2.qq.com/login?u={0}&p={1}&verifycode={2}&webqq_type=10&remember_uin=1&login2qq=1&aid=1003903&u1=http%3A%2F%2Fweb2.qq.com%2Floginproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&h=1&ptredirect=0&ptlang=2052&from_ui=1&pttype=1&dumy=&fp=loginerroralert&action=2-18-23557&mibao_css=m_webqq&t=1&g=1";

        private string VfWebQQ;
        private string SessionId;

        public string QQNumber { get; private set; }

        protected string ClientID { get; private set; }

        public event MessageEventHandler Message;
        protected virtual void OnMessage(MessageEventArgs e) 
        {
            var temp = Message;
            if (temp != null)
            {
                temp(this, e);
            }
        }

        private QQClient(string qq, string VfWebQQ, string clientid, string SessionId)
        {
            this.QQNumber = qq;
            this.VfWebQQ = VfWebQQ;
            this.ClientID = clientid;
            this.SessionId = SessionId;

            new Action(Revice).BeginInvoke(null, null);
        }

        private void Revice()
        {
            string url = "http://d.web2.qq.com/channel/poll2";
            string str = "r={\"clientid\":\"" + ClientID + "\",\"psessionid\":\"" + SessionId + "\",\"key\":0,\"ids\":[]}&clientid=" + ClientID + "&psessionid=" + SessionId;
            while (true) 
            {
                var res = HttpHelper.RequestString(url, Encoding.UTF8.GetBytes(str));
                dynamic json = JSON.Parse(res);
                var retcode = json.retcode;
                if (retcode == "0") 
                {
                    foreach (var item in json.result) 
                    {
                        switch ((string)item.poll_type) 
                        {
                            case "message": //好友消息
                                var msg = new MessageEventArgs(this, item.value);
                                OnMessage(msg);
                                break;
                            case "buddies_status_change"://好友上下线消息
                                break;
                            case "group_message":  //群消息
                                break;
                            case "system_message": //系统消息
                                break;
                            case "kick_message":   //被踢下线
                                return;
                        }
                    }
                }

                //被踢下线：{"retcode":0,"result":[{"poll_type":"kick_message","value":{"msg_id":60327,"from_uin":10000,"to_uin":335442412,"msg_id2":60328,"msg_type":48,"reply_ip":0,"show_reason":1,"reason":"\u60A8\u7684\u5E10\u53F7\u5728\u53E6\u4E00\u5730\u70B9\u767B\u5F55\uFF0C\u60A8\u5DF2\u88AB\u8FEB\u4E0B\u7EBF\u3002\u5982\u6709\u7591\u95EE\uFF0C\u8BF7\u767B\u5F55 safe.qq.com \u4E86\u89E3\u66F4\u591A\u3002"}}]}
                //收到消息：{"retcode":0,"result":[{"poll_type":"message","value":{"msg_id":27684,"from_uin":3701238459,"to_uin":335442412,"msg_id2":560507,"msg_type":9,"reply_ip":178847978,"time":1338061580,"content":[["font",{"size":9,"color":"ff00ff","style":[0,0,0],"name":"\u5FAE\u8F6F\u96C5\u9ED1"}],"11 "]}}]}
            }
        }

        /// <summary>登录WebQQ</summary>
        /// <param name="qq">QQ号码</param>
        /// <param name="pw">QQ密码</param>
        /// <param name="verify">用于返回验证码的回调</param>
        /// <returns>QQ客户端</returns>
        public static QQClient Login(string qq, string pw, VerifyCallback verify)
        {
            string result = HttpHelper.RequestString(string.Format(QQString1, qq, Random));

            string[] param = result.Split('\'');

            string verifyCode = param[3];
            if (param[1] == "1") //需要验证码
            {
                var data = HttpHelper.RequestData(string.Format(QQString2, qq, rnd.NextDouble().ToString()));
                verifyCode = verify(data).ToUpper();
            }

            //第一次登录
            //对密码进行MD5加密 pwd = Md5 ( Md5 ( Md5 ( pw ) + B1 ) + VC.ToUpper() )
            var pwd = Md5(pw, ToBytes(param[5]), verifyCode);

            result = HttpHelper.RequestString(string.Format(QQString3, qq, pwd, verifyCode));

            param = result.Split('\'');
            if (int.Parse(param[1]) == 0)
            {
                var clientid = Random;
                //第二次登录
                var url = "http://d.web2.qq.com/channel/login2";
                var data = "r={\"status\":\"\",\"ptwebqq\":\"" + HttpHelper.GetCookie("ptwebqq") + "\",\"passwd_sig\":\"\",\"clientid\":\"" + clientid + "\"}";
                result = HttpHelper.RequestString(url, Encoding.UTF8.GetBytes(data));

                var VfWebQQ = GetParameterValue(result, "vfwebqq");
                var SessionId = GetParameterValue(result, "psessionid");

                return new QQClient(qq, VfWebQQ, clientid, SessionId);
            }
            else
            {
                throw new Exception(param[9]);
            }
        }

        #region 登录相关辅助方法

        private static byte[] ToBytes(string str)
        {
            var bytes = new byte[8];
            for (var i = 0; i < 8; i++)
            {
                bytes[i] = byte.Parse(str.Substring((i * 4) + 2, 2), NumberStyles.HexNumber);
            }
            return bytes;
        }

        private static string GetParameterValue(string str, string key)
        {
            var l = key.Length;
            var i = str.IndexOf(key);
            if (i == -1)
            {
                return string.Empty;
            }
            return str.Substring(i + l + 3, str.IndexOf(',', i + l + 4) - i - l - 3).Replace("\"", "");
        }

        /// <summary>
        /// 连接两个字节数组
        /// </summary>
        /// <param name="b1"></param>
        /// <param name="b2"></param>
        /// <returns></returns>
        private static byte[] JoinBytes(byte[] b1, byte[] b2)
        {
            var b3 = new byte[b1.Length + b2.Length];
            Array.Copy(b1, b3, b1.Length);
            Array.Copy(b2, 0, b3, 16, b2.Length);
            return b3;
        }

        /// <summary>
        /// 将字符串加密为字节数组
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        private static byte[] Md5ToArray(string input)
        {
            return MD5.Create().ComputeHash(Encoding.Default.GetBytes(input));
        }

        /// <summary>
        /// 加密字符串,并转换十六进制默示的字符串
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        private static string Md5(string input)
        {
            var buffer = MD5.Create().ComputeHash(Encoding.Default.GetBytes(input));
            var builder = new StringBuilder();
            for (var i = 0; i < buffer.Length; i++)
            {
                builder.Append(buffer[i].ToString("X2"));
            }
            return builder.ToString();
        }

        /// <summary>
        /// 对一个字节数组加密,并转换十六进制默示的字符串
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        private static string Md5(byte[] input)
        {
            var buffer = MD5.Create().ComputeHash(input);
            var builder = new StringBuilder();
            for (var i = 0; i < buffer.Length; i++)
            {
                builder.Append(buffer[i].ToString("X2"));
            }
            return builder.ToString();
        }

        /// <summary>
        /// 对暗码进行加密
        /// </summary>
        /// <param name="password">QQ暗码</param>
        /// <param name="vcode">第一次查验用户状况时获取的key</param>
        /// <param name="verifyCode">验证码</param>
        /// <returns></returns>
        private static string Md5(string password, byte[] vcode, string verifyCode)
        {
            var b1 = Md5ToArray(password);
            var s1 = Md5(JoinBytes(b1, vcode));
            return Md5(s1 + verifyCode);
        }

        #endregion

        public bool Logout() 
        {
            string url = "http://d.web2.qq.com/channel/logout2?ids=&clientid={0}&psessionid={1}&t={2}";
            url = string.Format(url, ClientID, SessionId, Random);
            var s = HttpHelper.RequestString(url);
            dynamic josn = JSON.Parse(s);
            if (josn.retcode == "0") 
            {
                ClientID = null;
                SessionId = null;
                QQNumber = null;
                return true;
            }
            return false;
        }

        static readonly Random rnd = new Random();
        protected static string Random 
        {
            get 
            {
                return rnd.Next(10000000).ToString();
            }
        }

        IEnumerable<QQFriend> friends;
        public IEnumerable<QQFriend> Friends 
        {
            get 
            {
                if (friends == null) 
                {
                    friends = GetFriends();
                }
                return friends;
            }
        }

        private IEnumerable<QQFriend> GetFriends()
        {
            //得到所有好友的url  
            string url1 = "http://s.web2.qq.com/api/get_user_friends2";

            //得到群列表  
            string url2 = "http://s.web2.qq.com/api/get_group_name_list_mask2";

            //得到所有的好友json  

            String content = "{\"h\":\"hello\",\"vfwebqq\":\"" + VfWebQQ + "\"}";
            try
            {
                content = HttpUtility.UrlEncode(Encoding.UTF8.GetBytes(content));
                content = "r=" + content;
                string s = HttpHelper.RequestString(url1, Encoding.UTF8.GetBytes(content));
                dynamic josn = JSON.Parse(s);
                if (josn.retcode == "0")
                {
                    return QQFriend.Parse(this.VfWebQQ, josn.result);
                }
            }
            catch (Exception e)
            {

            }
            return null;
        }

        public bool SendMessage(QQFriend friend, string msg) 
        {
            msg = msg.Replace("\r\n", "\\\\n").Replace("\t", " ");

            //0:qq 1:msg 2:msgid 3:ClientID 4:SessionId
            //string str = "r={{\"to\":{0},\"face\":537,\"content\":\"[\"{1}\",[\"font\",{{\"name\":\"Arial\",\"size\":\"10\",\"style\":[0,0,0],\"color\":\"000000\"}}]]\",\"msg_id\":{2},\"clientid\":\"{3}\",\"psessionid\":\"{4}\"}}&clientid={3}&psessionid={4}";

            string str = "r=%7B%22to%22%3A{0}%2C%22face%22%3A546%2C%22content%22%3A%22%5B%5C%22{1}%5C%22%2C%5B%5C%22font%5C%22%2C%7B%5C%22name%5C%22%3A%5C%22%E5%AE%8B%E4%BD%93%5C%22%2C%5C%22size%5C%22%3A%5C%2210%5C%22%2C%5C%22style%5C%22%3A%5B0%2C0%2C0%5D%2C%5C%22color%5C%22%3A%5C%22000000%5C%22%7D%5D%5D%22%2C%22msg_id%22%3A{2}%2C%22clientid%22%3A%22{3}%22%2C%22psessionid%22%3A%22{4}%22%7D&clientid={3}&psessionid={4}";

            str = string.Format(str, friend.UIN, msg, Random, ClientID, SessionId);

            //str = HttpUtility.UrlEncode(Encoding.UTF8.GetBytes(str));

            var s = HttpHelper.RequestString("http://d.web2.qq.com/channel/send_buddy_msg2", Encoding.UTF8.GetBytes(str));
            //var josn = new JsonObject(s);
            //return josn["retcode"].Value == "0";
            dynamic json = JSON.Parse(s);
            return json.retcode == "0";
        }
    }

    public class QQFriend 
    {
        public string UIN { get; private set; }
        public string QQNumber { get; private set; }
        public string Nick { get; private set; }
        public string Name { get; private set; }
        public string Category { get; private set; }
        public int Face { get; private set; }
        public int VipLevel { get; private set; }

        public static IEnumerable<QQFriend> Parse(string vfwebqq, dynamic json)
        {
            var friends = new Dictionary<string, QQFriend>();
            var js1 = json.friends; //好友列表
            var js2 = json.marknames; //备注名
            var js3 = json.categories; //分组列表
            var js4 = json.vipinfo; //VIP信息
            var js5 = json.info; //好友信息

            var categories = new List<string>();
            foreach (var item in js3) 
            {
                categories.Add(item.name);
            }

            foreach (var item in js1) 
            {
                var uin = item.uin;

                string qurl = "http://s.web2.qq.com/api/get_friend_uin2?tuin=" + uin + "&verifysession=&type=1&code=&vfwebqq=" + vfwebqq + "&t=1334893936408";

                string result = HttpHelper.RequestString(qurl);

                dynamic j = JSON.Parse(result);

                friends[uin] = new QQFriend() { UIN = uin, QQNumber = j.result.account, Category = categories[int.Parse(item.categories)] };
            }

            foreach (var item in js4)
            {
                QQFriend friend;
                if (friends.TryGetValue(item.u, out friend)) 
                {
                    friend.VipLevel = int.Parse(item.vip_level);
                }
            }

            foreach (var item in js5)
            {
                QQFriend friend;
                if (friends.TryGetValue(item.uin, out friend))
                {
                    friend.Face = int.Parse(item.face);
                    friend.Nick = item.nick;
                }
            }

            return friends.Values;
        }
    }
}
